/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.amqp.rabbitmq;

import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.tianyun.cloud.amqp.rabbitmq.annotation.RabbitContainer;
import org.tianyun.cloud.amqp.rabbitmq.annotation.RabbitProducer;
import org.tianyun.cloud.amqp.rabbitmq.configuration.RabbitMqProperties;
import org.tianyun.cloud.amqp.rabbitmq.producer.RabbitProducerFactoryBean;
import org.tianyun.cloud.registrar.BeanRegistrar;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

/**
 * RabbitMQ注册，扫描Rabbit自定义注解自动创建Bean
 *
 * @auther ebert_chan
 */
public class RabbitContainerRegistrar extends BeanRegistrar {

    /*
     *
     * @see org.tianyun.cloud.registrar.BeanRegistrar#registerBeanDefinitions()
     */
    @Override
    public void registerBeanDefinitions() {
        Set<String> basePackages = this.getComponentScanningPackages();
        // 注册RabbitContainer
        this.registerRabbitContainer(basePackages);
        // 注册RabbitProducer
        this.registerRabbitProducers(basePackages);
    }

    /**
     * 扫描@RabbitContainer注解
     *
     * @param basePackages
     * @auther ebert_chan
     */
    public void registerRabbitContainer(Set<String> basePackages) {
        for (String basePackage : basePackages) {
            registerRabbitContainer(basePackage);
        }
    }

    private void registerRabbitContainer(String basePackage) {
        Set<BeanDefinition> candidateComponents = this.findCandidateComponents(basePackage, Component.class);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is not an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                if (annotationMetadata.isInterface()) {
                    continue;
                }
                String className = annotationMetadata.getClassName();
                this.resolveRabbitContainer(className);
            }
        }
    }

    private void resolveRabbitContainer(String className) {
        Class<?> clazz = this.loadClass(className);
        Method[] methods = MethodUtils.getMethodsWithAnnotation(clazz, RabbitContainer.class, false, true);
        if (methods == null) {
            return;
        }

        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            RabbitContainer rabbitContainer = AnnotatedElementUtils.findMergedAnnotation(method, RabbitContainer.class);
            Bean bean = method.getDeclaredAnnotation(Bean.class);

            if (!RabbitMqProperties.class.isAssignableFrom(method.getReturnType())) {
                throw new BeanCreationException("The @RabbitContainer annotation must be defined on RabbitmqProperties bean.");
            }

            String beanName = null;
            if (bean != null) {
                beanName = this.getFirstValue(bean.value());
                if (!StringUtils.hasText(beanName)) {
                    beanName = this.getFirstValue(bean.name());
                }
            }
            if (!StringUtils.hasText(beanName)) {
                beanName = method.getName();
            }

            Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(rabbitContainer);

            // 配置器
            BeanDefinitionRegistry registry = this.getBeanDefinitionRegistry();
            // @formatter:off
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RabbitMqConnectionFactoryConfigurer.class)
					.addConstructorArgValue(beanName)
					.addConstructorArgValue(attributes);
			// @formatter:on
            BeanDefinition configurerBeanDefinition = builder.getBeanDefinition();
            String configurerBeanName = "rabbitConnectionFactoryConfigurer#" + i;
            registry.registerBeanDefinition(configurerBeanName, configurerBeanDefinition);
        }
    }

    /**
     * 扫描@RabbitProducer注解，注册bean
     *
     * @param basePackages
     * @auther ebert_chan
     */
    public void registerRabbitProducers(Set<String> basePackages) {
        for (String basePackage : basePackages) {
            this.registerRabbitProducers(basePackage);
        }
    }

    private void registerRabbitProducers(String basePackage) {
        Set<BeanDefinition> candidateComponents = this.findCandidateComponents(basePackage, RabbitProducer.class);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();

                Assert.isTrue(annotationMetadata.isInterface(), "@RabbitProducer can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(RabbitProducer.class.getCanonicalName());
                String name = this.getName(attributes);
                String exchange = (String) attributes.get("exchange");
                String rabbitTemplate = (String) attributes.get("rabbitTemplate");
                boolean primary = (Boolean) attributes.get("primary");
                String className = annotationMetadata.getClassName();
                Class<?> clazz = this.loadClass(className);

                // @formatter:off
				BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(RabbitProducerFactoryBean.class)
						.addPropertyValue("type", clazz)
						.addPropertyValue("exchange", exchange)
						.addPropertyValue("rabbitTemplate", rabbitTemplate);
				// @formatter:on
                AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
                beanDefinition.setPrimary(primary);
                annotatedBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

                BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, StringUtils.hasText(name) ? name : className);
                BeanDefinitionReaderUtils.registerBeanDefinition(holder, this.getBeanDefinitionRegistry());
            }
        }
    }

}
